/*
    The MIT License

    Copyright (c) 2012 Sam Beckett

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to deal
    in the Software without restriction, including without limitation the rights
    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included in
    all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    THE SOFTWARE.
*/
namespace AblePlugins.FedEx
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using AblePlugins.FedEx.RateService9;
    using CommerceBuilder.Common;
    using CommerceBuilder.Orders;
    using CommerceBuilder.Shipping;
    using CommerceBuilder.Shipping.Providers;
    using CommerceBuilder.Stores;
    using CommerceBuilder.Utility;
    using FedExAddress = AblePlugins.FedEx.RateService9.Address;

    public class FedExProvider : ShippingProviderBase
    {
        private const string LIVEURL = "https://ws.fedex.com:443/web-services/rate";
        private const string TESTURL = "https://wsbeta.fedex.com:443/web-services/rate";

        private string _AccountNumber = string.Empty;
        public string AccountNumber
        {
            get { return _AccountNumber; }
            set { _AccountNumber = value; }
        }

        private string _MeterNumber = string.Empty;
        public string MeterNumber
        {
            get { return _MeterNumber; }
            set { _MeterNumber = value; }
        }

        private bool _EnablePackageBreakup = true;
        public bool EnablePackageBreakup
        {
            get { return _EnablePackageBreakup; }
            set { _EnablePackageBreakup = value; }
        }

        private bool _UseTestMode = false;
        public bool UseTestMode
        {
            get { return _UseTestMode; }
            set { _UseTestMode = value; }
        }

        private bool _AccountActive = false;
        public bool AccountActive
        {
            get { return _AccountActive; }
            set { _AccountActive = value; }
        }

        private DropoffType _DropoffType = DropoffType.REGULAR_PICKUP;
        public DropoffType DropoffType
        {
            get
            {
                return _DropoffType;
            }
            set
            {
                _DropoffType = value;
            }
        }

        private PackagingType _PackagingType = PackagingType.YOUR_PACKAGING;
        public PackagingType PackagingType
        {
            get
            {
                return _PackagingType;
            }
            set
            {
                _PackagingType = value;
            }
        }

        private string _PayorAccountNumber = string.Empty;
        public string PayorAccountNumber
        {
            get
            {
                return _PayorAccountNumber;
            }
            set
            {
                _PayorAccountNumber = value;
            }
        }

        private string _Key = string.Empty;
        public string Key
        {
            get { return _Key; }
            set { _Key = value; }
        }

        private string _Password = string.Empty;
        public string Password
        {
            get { return _Password; }
            set { _Password = value; }
        }

        private Decimal _MinPackageWeight = (decimal)0.1;
        public Decimal MinPackageWeight
        {
            get { return _MinPackageWeight; }
            set { _MinPackageWeight = value; }
        }

        //max weight for FedEx is 150lb
        private Decimal _MaxPackageWeight = 150;
        public Decimal MaxPackageWeight
        {
            get { return _MaxPackageWeight; }
            set { _MaxPackageWeight = value; }
        }

        //string link = "http://www.fedex.com/Tracking?ascend_header=1&clienttype=dotcom&tracknumbers={0}";
        private static string _DefaultTrackingUrl = "http://www.fedex.com/Tracking?tracknumbers={0}";
        private string _TrackingUrl;
        public string TrackingUrl
        {
            get
            {
                if (string.IsNullOrEmpty(_TrackingUrl))
                {
                    return _DefaultTrackingUrl;
                }
                else
                {
                    return _TrackingUrl;
                }
            }
            set { _TrackingUrl = value; }
        }

        private bool _IncludeDeclaredValue = true;
        public bool IncludeDeclaredValue
        {
            get { return _IncludeDeclaredValue; }
            set { _IncludeDeclaredValue = value; }
        }

        public override string Name
        {
            get { return "FedEx"; }
        }

        public override string Version
        {
            get
            {
                VersionId v = new VersionId();
                return "Web Services v" + v.Major + "." + v.Minor;
            }
        }

        private static Dictionary<string, string> _services;
        static FedExProvider()
        {
            _services = new Dictionary<string, string>();
            _services.Add(ServiceType.EUROPE_FIRST_INTERNATIONAL_PRIORITY.ToString(), "FedEx Europe First International Priority");
            _services.Add(ServiceType.FEDEX_1_DAY_FREIGHT.ToString(), "FedEx 1 Day Freight");
            _services.Add(ServiceType.FEDEX_2_DAY.ToString(), "FedEx 2 Day");
            _services.Add(ServiceType.FEDEX_2_DAY_FREIGHT.ToString(), "FedEx 2 Day Freight");
            _services.Add(ServiceType.FEDEX_3_DAY_FREIGHT.ToString(), "FedEx 3 Day Freight");
            _services.Add(ServiceType.FEDEX_EXPRESS_SAVER.ToString(), "FedEx Express Saver");
            _services.Add(ServiceType.FEDEX_GROUND.ToString(), "FedEx Ground");
            _services.Add(ServiceType.FIRST_OVERNIGHT.ToString(), "FedEx Overnight");
            _services.Add(ServiceType.GROUND_HOME_DELIVERY.ToString(), "FedEx Ground Home Delivery");
            _services.Add(ServiceType.INTERNATIONAL_ECONOMY.ToString(), "FedEx International Economy");
            _services.Add(ServiceType.INTERNATIONAL_ECONOMY_FREIGHT.ToString(), "FedEx International Economy Freight");
            _services.Add(ServiceType.INTERNATIONAL_FIRST.ToString(), "FedEx International First");
            _services.Add(ServiceType.INTERNATIONAL_PRIORITY.ToString(), "FedEx International Priority");
            _services.Add(ServiceType.INTERNATIONAL_PRIORITY_FREIGHT.ToString(), "FedEx International Priority Freight");
            _services.Add(ServiceType.PRIORITY_OVERNIGHT.ToString(), "FedEx Priority Overnight");
            _services.Add(ServiceType.STANDARD_OVERNIGHT.ToString(), "FedEx Standard Overnight");
        }

        #region Interface_Implementation_methods

        public override string GetLogoUrl(ClientScriptManager cs)
        {
            if (cs != null)
                return cs.GetWebResourceUrl(this.GetType(), "AblePlugins.FedEx.Logo.gif");
            return string.Empty;
        }

        public override string GetConfigUrl(ClientScriptManager cs)
        {
            return "FedEx/Default.aspx";
        }

        public override string Description
        {
            get { return "The FedEx Connector for AbleCommerce Gold can generate real-time shipping rates for your packages and provide tracking details."; }
        }

        public override void Initialize(int ShipGatewayId, IDictionary<string, string> ConfigurationData)
        {
            base.Initialize(ShipGatewayId, ConfigurationData);
            //INITIALIZE MY FIELDS
            if (ConfigurationData.ContainsKey("AccountNumber")) AccountNumber = ConfigurationData["AccountNumber"];
            if (ConfigurationData.ContainsKey("MeterNumber")) MeterNumber = ConfigurationData["MeterNumber"];
            if (ConfigurationData.ContainsKey("Key")) Key = ConfigurationData["Key"];
            if (ConfigurationData.ContainsKey("Password")) Password = ConfigurationData["Password"];
            if (ConfigurationData.ContainsKey("PayorAccountNumber")) PayorAccountNumber = ConfigurationData["PayorAccountNumber"];
            if (ConfigurationData.ContainsKey("EnablePackageBreakup")) EnablePackageBreakup = AlwaysConvert.ToBool(ConfigurationData["EnablePackageBreakup"], true);
            if (ConfigurationData.ContainsKey("MinPackageWeight")) MinPackageWeight = AlwaysConvert.ToDecimal(ConfigurationData["MinPackageWeight"], (decimal)MinPackageWeight);
            if (ConfigurationData.ContainsKey("MaxPackageWeight")) MaxPackageWeight = AlwaysConvert.ToDecimal(ConfigurationData["MaxPackageWeight"], (decimal)MaxPackageWeight);
            if (ConfigurationData.ContainsKey("IncludeDeclaredValue")) IncludeDeclaredValue = AlwaysConvert.ToBool(ConfigurationData["IncludeDeclaredValue"], true);
            if (ConfigurationData.ContainsKey("UseTestMode")) UseTestMode = AlwaysConvert.ToBool(ConfigurationData["UseTestMode"], false);
            if (ConfigurationData.ContainsKey("AccountActive")) AccountActive = AlwaysConvert.ToBool(ConfigurationData["AccountActive"], false);
            if (ConfigurationData.ContainsKey("DropoffType")) DropoffType = (DropoffType)AlwaysConvert.ToEnum(typeof(DropoffType), ConfigurationData["DropoffType"], DropoffType.REGULAR_PICKUP, true);
            if (ConfigurationData.ContainsKey("PackagingType")) PackagingType = (PackagingType)AlwaysConvert.ToEnum(typeof(PackagingType), ConfigurationData["PackagingType"], PackagingType.YOUR_PACKAGING, true);
            if (ConfigurationData.ContainsKey("TrackingUrl")) TrackingUrl = ConfigurationData["TrackingUrl"];
        }

        public override IDictionary<string, string> GetConfigData()
        {
            IDictionary<string, string> configData = base.GetConfigData();
            configData.Add("AccountNumber", this.AccountNumber);
            configData.Add("MeterNumber", this.MeterNumber);
            configData.Add("Key", this.Key);
            configData.Add("Password", this.Password);
            configData.Add("PayorAccountNumber", this.PayorAccountNumber);
            configData.Add("EnablePackageBreakup", this.EnablePackageBreakup.ToString());
            configData.Add("MinPackageWeight", this.MinPackageWeight.ToString());
            configData.Add("MaxPackageWeight", this.MaxPackageWeight.ToString());
            configData.Add("IncludeDeclaredValue", this.IncludeDeclaredValue.ToString());
            configData.Add("UseTestMode", this.UseTestMode.ToString());
            configData.Add("AccountActive", this.AccountActive.ToString());
            configData.Add("DropoffType", this.DropoffType.ToString());
            configData.Add("PackagingType", this.PackagingType.ToString());
            configData.Add("TrackingUrl", this.TrackingUrl);
            return configData;
        }

        public override ListItem[] GetServiceListItems()
        {
            List<ListItem> services = new List<ListItem>();
            string value;
            foreach (string key in _services.Keys)
            {
                value = _services[key];
                services.Add(new ListItem(value, key));
            }
            return services.ToArray();
        }

        public override ShipRateQuote GetShipRateQuote(Warehouse origin, CommerceBuilder.Users.Address destination, IList<BasketItem> contents, string serviceCode)
        {
            Dictionary<string, ProviderShipRateQuote> allQuotes = GetAllServiceQuotes(origin, destination, contents);
            if ((allQuotes != null) && allQuotes.ContainsKey(serviceCode))
                return allQuotes[serviceCode];
            else
                return null;
        }

        public override TrackingSummary GetTrackingSummary(TrackingNumber trackingNumber)
        {
            TrackingSummary summary = new TrackingSummary();
            summary.TrackingResultType = TrackingResultType.ExternalLink;
            summary.TrackingLink = string.Format(TrackingUrl, HttpUtility.UrlEncode(trackingNumber.TrackingNumberData));
            return summary;
        }

        #endregion Interface_Implementation_methods

        #region Implementation_Support_methods

        private Dictionary<string, ProviderShipRateQuote> GetAllServiceQuotes(Warehouse origin, CommerceBuilder.Users.Address destination, IList<BasketItem> contents)
        {
            string cacheKey = StringHelper.CalculateMD5Hash(Misc.GetClassId(this.GetType()) + "_" + origin.Id.ToString() + "_" + destination.Id.ToString() + "_" + contents.GenerateContentHash());
            HttpContext context = HttpContext.Current;
            if ((context != null) && (context.Items.Contains(cacheKey)))
                return (Dictionary<string, ProviderShipRateQuote>)context.Items[cacheKey];

            //VERIFY WE HAVE A DESTINATION COUNTRY
            if (string.IsNullOrEmpty(destination.CountryCode)) return null;

            PackageList plist = PreparePackages(origin, destination, contents);
            if (plist == null || plist.Count == 0) return null;

            Dictionary<string, ProviderShipRateQuote> allQuotes;
            allQuotes = GetProviderQuotes(origin, destination, plist);

            if (context != null) context.Items.Add(cacheKey, allQuotes);

            return allQuotes;
        }

        private string GetServiceName(String serviceCode)
        {
            string serviceName = string.Empty;
            if (_services.ContainsKey(serviceCode)) serviceName = _services[serviceCode];
            if (string.IsNullOrEmpty(serviceName)) serviceName = serviceCode + " : Unknown";
            return serviceName;
        }

        private PackageList PreparePackages(Warehouse origin, CommerceBuilder.Users.Address destination, IList<BasketItem> contents)
        {
            PackageList plist = PackageManager.GetPackageList(contents);
            if (plist == null || plist.Count == 0) return null;

            ProviderUnits pUnits = GetProviderUnits(origin.Country);
            //GET UNITS USED BY STORE
            Store store = AbleContext.Current.Store;
            MeasurementUnit storeMeasurementUnit = store.MeasurementUnit;
            WeightUnit storeWeightUnit = store.WeightUnit;

            bool requireMC = storeMeasurementUnit != pUnits.MeasurementUnit;
            bool requireWC = storeWeightUnit != pUnits.WeightUnit;

            if (requireMC && requireWC)
            {
                plist.ConvertBoth(pUnits.WeightUnit, pUnits.MeasurementUnit);
            }
            else if (requireWC)
            {
                plist.ConvertWeight(pUnits.WeightUnit);
            }
            else if (requireMC)
            {
                plist.ConvertDimensions(pUnits.MeasurementUnit);
            }

            Decimal maxWeight = LocaleHelper.ConvertWeight(WeightUnit.Pounds, MaxPackageWeight, pUnits.WeightUnit);
            Decimal minWeight = LocaleHelper.ConvertWeight(WeightUnit.Pounds, MinPackageWeight, pUnits.WeightUnit);
            if (EnablePackageBreakup && maxWeight > 0)
            {
                //compose packages (splits items larger than the maximum carrier weight)                
                plist.Compose(maxWeight, minWeight);
            }
            else
            {
                plist.EnsureMinimumWeight(minWeight);
            }

            //convert weight and dimensions to whole numbers
            plist.ConvertDimsToWholeNumbers();
            plist.RoundWeight(1);

            return plist;
        }


        #endregion Implementation_Support_methods

        struct ProviderUnits
        {
            public MeasurementUnit MeasurementUnit;
            public WeightUnit WeightUnit;
        }

        private Dictionary<string, ProviderShipRateQuote> GetProviderQuotes(Warehouse origin, CommerceBuilder.Users.Address destination, PackageList packageList)
        {
            RateRequest request = CreateRateRequest(origin, destination, packageList);
            RateService rateService = new RateService();
            rateService.Url = this.UseTestMode ? TESTURL : LIVEURL;

            // record request to debug log
            if (this.UseDebugMode)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine();
                sb.AppendLine("Web Service URL: " + rateService.Url);
                string serializedRequest = new UTF8Encoding().GetString(XmlUtility.Serialize(request));
                serializedRequest = serializedRequest.Replace("<Key>" + this.Key, "<Key>xxxx");
                serializedRequest = serializedRequest.Replace("<Password>" + this.Password, "<Password>xxxx");
                serializedRequest = serializedRequest.Replace("<AccountNumber>" + this.AccountNumber, "<AccountNumber>xxxx" + this.AccountNumber.Substring(4));
                serializedRequest = serializedRequest.Replace("<MeterNumber>" + this.MeterNumber, "<MeterNumber>xxxx" + this.MeterNumber.Substring(4));
                sb.Append(serializedRequest);
                this.RecordCommunication("AblePlugins.FedEx", CommunicationDirection.Send, sb.ToString());
            }

            // This is the call to the web service passing in a RateRequest and returning a RateReply
            RateReply reply;
            try
            {
                reply = rateService.getRates(request); // Service call
            }
            catch (System.Web.Services.Protocols.SoapException se)
            {
                Logger.Error("Soap Exception", se);
                Logger.Debug(se.Detail.InnerXml);
                return new Dictionary<string, ProviderShipRateQuote>();
            }

            // record reply to debug log
            if (this.UseDebugMode)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine();
                sb.Append(new UTF8Encoding().GetString(XmlUtility.Serialize(reply)));
                this.RecordCommunication("AblePlugins.FedEx", CommunicationDirection.Receive, sb.ToString());
            }

            if (reply.HighestSeverity == NotificationSeverityType.SUCCESS || reply.HighestSeverity == NotificationSeverityType.NOTE || reply.HighestSeverity == NotificationSeverityType.WARNING) // check if the call was successful
            {
                //ShowRateReply(reply);
                return ParseRates(reply);
            }
            else
            {
                //Console.WriteLine(reply.Notifications[0].Message);
                return new Dictionary<string, ProviderShipRateQuote>();
            }
        }

        private Dictionary<string, ProviderShipRateQuote> ParseRates(RateReply reply)
        {
            Dictionary<string, ProviderShipRateQuote> quotes = new Dictionary<string, ProviderShipRateQuote>();
            for (int i = 0; i < reply.RateReplyDetails.Length; i++)
            {
                RateReplyDetail rdetail = reply.RateReplyDetails[i];
                RatedShipmentDetail rsdetail = GetRatedShipmentDetail(rdetail.RatedShipmentDetails);
                if (rsdetail != null)
                {
                    ProviderShipRateQuote psrq = new ProviderShipRateQuote();
                    psrq.ServiceCode = rdetail.ServiceType.ToString();
                    psrq.ServiceName = GetServiceName(psrq.ServiceCode);
                    psrq.Rate = rsdetail.ShipmentRateDetail.TotalNetCharge.Amount;
                    quotes.Add(psrq.ServiceCode, psrq);
                }
            }
            return quotes;
        }

        private RatedShipmentDetail GetRatedShipmentDetail(RatedShipmentDetail[] rsdetails)
        {
            //For this integration of FedEx we are only concerned with Payor Account shipping rates
            foreach (RatedShipmentDetail rsd in rsdetails)
            {
                if (rsd.ShipmentRateDetail.RateType == ReturnedRateType.PAYOR_ACCOUNT_PACKAGE
                    || rsd.ShipmentRateDetail.RateType == ReturnedRateType.PAYOR_ACCOUNT_SHIPMENT)
                {
                    return rsd;
                }
            }
            return null;
        }

        private RateRequest CreateRateRequest(Warehouse origin, CommerceBuilder.Users.Address destination, PackageList packageList)
        {
            // Build the RateRequest
            RateRequest request = new RateRequest();
            //
            request.WebAuthenticationDetail = new WebAuthenticationDetail();
            request.WebAuthenticationDetail.UserCredential = new WebAuthenticationCredential();
            request.WebAuthenticationDetail.UserCredential.Key = this.Key;
            request.WebAuthenticationDetail.UserCredential.Password = this.Password;
            //
            request.ClientDetail = new ClientDetail();
            request.ClientDetail.AccountNumber = this.AccountNumber;
            request.ClientDetail.MeterNumber = this.MeterNumber;
            //
            request.TransactionDetail = new TransactionDetail();
            request.TransactionDetail.CustomerTransactionId = "v9 RateService Request";
            //
            request.Version = new VersionId(); // WSDL version information, value is automatically set from wsdl            
            // 
            // Origin information
            request.CarrierCodes = new CarrierCodeType[2];
            request.CarrierCodes[0] = CarrierCodeType.FDXE;
            request.CarrierCodes[1] = CarrierCodeType.FDXG;
            request.RequestedShipment = new RequestedShipment();
            request.RequestedShipment.Shipper = new Party();
            request.RequestedShipment.Shipper.Address = new FedExAddress();
            request.RequestedShipment.Shipper.Address.StreetLines = new string[1] { origin.Address1 };
            request.RequestedShipment.Shipper.Address.City = origin.City;
            request.RequestedShipment.Shipper.Address.StateOrProvinceCode = StringHelper.Truncate(origin.Province, 2);
            request.RequestedShipment.Shipper.Address.PostalCode = origin.PostalCode;
            request.RequestedShipment.Shipper.Address.CountryCode = origin.CountryCode;
            //
            // Destination Information
            request.RequestedShipment.Recipient = new Party();
            request.RequestedShipment.Recipient.Address = new FedExAddress();
            request.RequestedShipment.Recipient.Address.StreetLines = new string[1] { destination.Address1 };
            request.RequestedShipment.Recipient.Address.City = destination.City;
            request.RequestedShipment.Recipient.Address.StateOrProvinceCode = StringHelper.Truncate(destination.Province, 2);
            request.RequestedShipment.Recipient.Address.PostalCode = destination.PostalCode;
            request.RequestedShipment.Recipient.Address.CountryCode = destination.CountryCode;
            if (destination.Residence)
            {
                request.RequestedShipment.Recipient.Address.Residential = destination.Residence;
                request.RequestedShipment.Recipient.Address.ResidentialSpecified = true;
            }
            //
            // Payment Information
            request.RequestedShipment.ShippingChargesPayment = new Payment();
            request.RequestedShipment.ShippingChargesPayment.PaymentType = PaymentType.SENDER;
            request.RequestedShipment.ShippingChargesPayment.PaymentTypeSpecified = true;
            if (!string.IsNullOrEmpty(this.PayorAccountNumber))
            {
                request.RequestedShipment.ShippingChargesPayment.Payor = new Payor();
                request.RequestedShipment.ShippingChargesPayment.Payor.AccountNumber = this.PayorAccountNumber;
            }

            // Packaging Information
            request.RequestedShipment.DropoffType = this.DropoffType;
            request.RequestedShipment.PackagingType = this.PackagingType;
            request.RequestedShipment.PackagingTypeSpecified = true;
            request.RequestedShipment.ServiceTypeSpecified = false;
            request.RequestedShipment.RateRequestTypes = new RateRequestType[1];
            request.RequestedShipment.RateRequestTypes[0] = RateRequestType.ACCOUNT;

            // rate shipment as if sent from next business day at 10AM
            DateTime localNow = LocaleHelper.LocalNow;
            DateTime nextBusinessDay = new DateTime(localNow.Year, localNow.Month, localNow.Day, 10, 0, 0, DateTimeKind.Local).AddDays(1);
            if (nextBusinessDay.DayOfWeek == DayOfWeek.Saturday) nextBusinessDay = nextBusinessDay.AddDays(2);
            else if (nextBusinessDay.DayOfWeek == DayOfWeek.Sunday) nextBusinessDay = nextBusinessDay.AddDays(1);
            request.RequestedShipment.ShipTimestamp = nextBusinessDay;
            request.RequestedShipment.ShipTimestampSpecified = true;

            // Package List
            int totalPackages = 0;
            foreach (Package pkg in packageList) { totalPackages += pkg.Multiplier; }
            request.RequestedShipment.PackageCount = totalPackages.ToString();
            request.RequestedShipment.PackageDetail = RequestedPackageDetailType.INDIVIDUAL_PACKAGES;
            request.RequestedShipment.PackageDetailSpecified = true;
            request.RequestedShipment.RequestedPackageLineItems = new RequestedPackageLineItem[packageList.Count];

            ProviderUnits pUnits = GetProviderUnits(origin.Country);
            for (int i = 0; i < packageList.Count; i++)
            {
                Package pkg = packageList[i];
                request.RequestedShipment.RequestedPackageLineItems[i] = new RequestedPackageLineItem();
                request.RequestedShipment.RequestedPackageLineItems[i].GroupNumber = (i + 1).ToString();
                request.RequestedShipment.RequestedPackageLineItems[i].GroupPackageCount = pkg.Multiplier.ToString();
                // package weight
                request.RequestedShipment.RequestedPackageLineItems[i].Weight = new Weight();
                request.RequestedShipment.RequestedPackageLineItems[i].Weight.Units = GetWeightUnits(pUnits.WeightUnit);
                request.RequestedShipment.RequestedPackageLineItems[i].Weight.Value = (decimal)pkg.Weight;
                if (pkg.Length > 0 || pkg.Width > 0 || pkg.Height > 0)
                {
                    // package dimensions
                    request.RequestedShipment.RequestedPackageLineItems[i].Dimensions = new Dimensions();
                    request.RequestedShipment.RequestedPackageLineItems[i].Dimensions.Length = pkg.Length.ToString();
                    request.RequestedShipment.RequestedPackageLineItems[i].Dimensions.Width = pkg.Width.ToString();
                    request.RequestedShipment.RequestedPackageLineItems[i].Dimensions.Height = pkg.Height.ToString();
                    request.RequestedShipment.RequestedPackageLineItems[i].Dimensions.Units = GetLinearUnits(pUnits.MeasurementUnit);
                }
                // insurance
                if (IncludeDeclaredValue)
                {
                    request.RequestedShipment.RequestedPackageLineItems[i].InsuredValue = new Money(); // insured value
                    request.RequestedShipment.RequestedPackageLineItems[i].InsuredValue.Amount = (decimal)pkg.RetailValue;
                    request.RequestedShipment.RequestedPackageLineItems[i].InsuredValue.Currency = AbleContext.Current.Store.BaseCurrency.ISOCode;
                }
            }

            return request;
        }

        private ProviderUnits GetProviderUnits(Country originCountry)
        {
            ProviderUnits pUnits = new ProviderUnits();
            //DETERMINE FEDEX REQUIRED UNITS
            string[] imperialUnits = { "US", "PR", "DO", "BS" };
            if (Array.IndexOf(imperialUnits, originCountry.CountryCode) > -1)
            {
                pUnits.MeasurementUnit = MeasurementUnit.Inches;
                pUnits.WeightUnit = WeightUnit.Pounds;
            }
            else
            {
                pUnits.MeasurementUnit = MeasurementUnit.Centimeters;
                pUnits.WeightUnit = WeightUnit.Kilograms;
            }
            return pUnits;
        }

        private string GetMeasurementUnitCode(MeasurementUnit munit)
        {
            if (munit == MeasurementUnit.Centimeters) return "CM";
            return "IN";
        }

        private string GetWeightUnitCode(WeightUnit wunit)
        {
            switch (wunit)
            {
                case WeightUnit.Grams:
                    return "GMS";
                case WeightUnit.Kilograms:
                    return "KGS";
                case WeightUnit.Ounces:
                    return "OZS";
                case WeightUnit.Pounds:
                    return "LBS";
                default:
                    return "";
            }
        }

        private WeightUnits GetWeightUnits(WeightUnit wu)
        {
            switch (wu)
            {
                case WeightUnit.Kilograms:
                    return WeightUnits.KG;
                case WeightUnit.Pounds:
                    return WeightUnits.LB;
                default:
                    return WeightUnits.LB;
            }
        }

        private LinearUnits GetLinearUnits(MeasurementUnit mu)
        {
            if (mu == MeasurementUnit.Centimeters) return LinearUnits.CM;
            return LinearUnits.IN;
        }
    }
}